using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;

namespace XmlStudio.Validators {
    /// <summary>
    /// This static class provides attached properties which supply 
    /// validation of the text in a TextBox, using regular expressions.
    /// </summary>
    public static class RegexValidator {
        #region Static Constructor

        static RegexValidator() {
            RegexTextProperty = DependencyProperty.RegisterAttached(
                "RegexText",
                typeof(string),
                typeof(RegexValidator),
                new UIPropertyMetadata(null, OnAttachedPropertyChanged));

            ErrorMessageProperty = DependencyProperty.RegisterAttached(
                "ErrorMessage",
                typeof(string),
                typeof(RegexValidator),
                new UIPropertyMetadata(null, OnAttachedPropertyChanged));
        }

        #endregion // Static Constructor

        #region Attached Properties

        #region ErrorMessage

        /// <summary>
        /// Identifies the RegexValidator's ErrorMessage attached property.  
        /// This field is read-only.
        /// </summary>
        public static readonly DependencyProperty ErrorMessageProperty;

        /// <summary>
        /// Returns the error message used when validation fails for the
        /// specified TextBox.
        /// </summary>
        /// <param name="textBox">The TextBox whose error message is returned.</param>
        public static string GetErrorMessage(TextBox textBox) {
            return textBox.GetValue(ErrorMessageProperty) as string;
        }

        /// <summary>
        /// Sets the error message used when validation fails for the 
        /// specified TextBox.
        /// </summary>
        /// <param name="textBox">The TextBox being validated.</param>
        /// <param name="value">The error message.</param>
        public static void SetErrorMessage(TextBox textBox, string value) {
            textBox.SetValue(ErrorMessageProperty, value);
        }

        #endregion // ErrorMessage

        #region RegexText

        /// <summary>
        /// Identifies the RegexValidator's RegexText attached property.  
        /// This field is read-only.
        /// </summary>
        public static readonly DependencyProperty RegexTextProperty;

        /// <summary>
        /// Returns the regular expression used to validate the specified TextBox.
        /// </summary>
        /// <param name="textBox">The TextBox whose regular expression is returned.</param>
        public static string GetRegexText(TextBox textBox) {
            return textBox.GetValue(RegexTextProperty) as string;
        }

        /// <summary>
        /// Sets the regular expression used to validate the 
        /// specified TextBox.
        /// </summary>
        /// <param name="textBox">The TextBox being validated.</param>
        /// <param name="value">The regular expression.</param>
        public static void SetRegexText(TextBox textBox, string value) {
            textBox.SetValue(RegexTextProperty, value);
        }

        #endregion // RegexText

        #endregion // Attached Properties

        #region Event Handling Methods

        #region OnAttachedPropertyChanged

        /// <summary>
        /// Invoked whenever an attached property of the 
        /// RegexValidator is modified for a TextBox.
        /// </summary>
        /// <param name="depObj">A TextBox.</param>
        /// <param name="e"></param>
        static void OnAttachedPropertyChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) {
            TextBox textBox = depObj as TextBox;
            if(textBox == null)
                throw new InvalidOperationException(
                    "The RegexValidator can only be used with a TextBox.");

            VerifyRegexValidationRule(textBox);
        }

        #endregion // OnAttachedPropertyChanged

        #endregion // Event Handling Methods

        #region Private Helpers

        #region GetRegexValidationRuleForTextBox

        /// <summary>
        /// Returns a RegexValidationRule to be used for validating the specified TextBox.
        /// If the TextBox is not yet initialized, this method returns null.
        /// </summary>
        static RegexValidationRule GetRegexValidationRuleForTextBox(TextBox textBox) {
            if((bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue)) {
                return null;
            }

            if(!textBox.IsInitialized) {
                // If the TextBox.Text property is bound, but the TextBox is not yet
                // initialized, the property's binding can be null.  In that situation,
                // hook its Initialized event and verify the validation rule again when 
                // that event fires.  At that point in time, the Text property's binding
                // will be non-null.
                EventHandler callback = null;
                callback = delegate {
                    textBox.Initialized -= callback;
                    VerifyRegexValidationRule(textBox);
                };
                textBox.Initialized += callback;
                return null;
            }

            // Get the binding expression associated with the TextBox's Text property.
            BindingExpression expression = textBox.GetBindingExpression(TextBox.TextProperty);
            if(expression == null)
                throw new InvalidOperationException(
                    "The TextBox's Text property must be bound for the RegexValidator to validate it.");

            // Get the binding which owns the binding expression.
            Binding binding = expression.ParentBinding;
            if(binding == null)
                throw new ApplicationException(
                    "Unexpected situation: the TextBox.Text binding expression has no param binding.");

            // Look for an existing instance of the RegexValidationRule class in the
            // binding.  If there is more than one instance in the ValidationRules
            // then throw an exception because we don't know which one to modify.
            RegexValidationRule regexRule = null;
            foreach(ValidationRule rule in binding.ValidationRules) {
                if(rule is RegexValidationRule) {
                    if(regexRule == null)
                        regexRule = rule as RegexValidationRule;
                    else
                        throw new InvalidOperationException(
                            "There should not be more than one RegexValidationRule in a Binding's ValidationRules.");
                }
            }

            // If the TextBox.Text property's binding does not yet have an 
            // instance of RegexValidationRule in its ValidationRules,
            // add an instance now and return it.
            if(regexRule == null) {
                regexRule = new RegexValidationRule();
                binding.ValidationRules.Add(regexRule);
            }

            return regexRule;
        }

        #endregion // GetRegexValidationRuleForTextBox

        #region VerifyRegexValidationRule

        /// <summary>
        /// Creates or modifies the RegexValidationRule in the TextBox's 
        /// Text property binding to use the current values of the attached 
        /// properties exposed by this class.
        /// </summary>
        /// <param name="textBox">The TextBox being validated.</param>
        static void VerifyRegexValidationRule(TextBox textBox) {
            RegexValidationRule regexRule = GetRegexValidationRuleForTextBox(textBox);
            if(regexRule != null) {
                regexRule.RegexText =
                    textBox.GetValue(RegexValidator.RegexTextProperty) as string;

                regexRule.ErrorMessage =
                    textBox.GetValue(RegexValidator.ErrorMessageProperty) as string;
            }
        }

        #endregion // VerifyRegexValidationRule

        #endregion // Private Helpers
    }
}
